package com.sk.util;

import lombok.Cleanup;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

import java.io.*;
import java.net.URL;
import java.net.URLConnection;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;

/**
 * 文件处理工具。
 *
 * @author smy
 */
@Slf4j
public class FileUtil {
    public static final String separator = "/";

    /**
     * 流关闭函数
     *
     * @param closeables 可关闭对象
     */
    public static void close(Closeable... closeables) {
        for (Closeable closeable : closeables) {
            if (closeable == null) {
                continue;
            }
            try {
                closeable.close();
            } catch (IOException ignored) {

            }
        }
    }

    /**
     * 获取文件后缀
     *
     * @param path 文件路径
     * @return 文件后缀名
     */
    public static String getNameExtension(String path) {
        if (path == null) {
            return null;
        }
        int i = path.lastIndexOf(".");
        if (i <= 0 || i == path.length() - 1) {
            return null;
        }
        return path.substring(i + 1).toLowerCase();
    }


    public static String getName(String path) {
        return new File(path).getName();
    }

    /**
     * 绝对路径
     *
     * @param file 文件
     * @return 绝对路径
     */
    @SneakyThrows
    public static String getCanonicalPath(File file) {
        return file.getCanonicalPath().replace(File.separator, separator);
    }

    /**
     * 相对路径
     *
     * @param dir      目录
     * @param filePath 文件
     * @return 文件相对路径
     */
    public static String getAbsolutePath(String dir, String filePath) {
        String s1 = dir.replace("\\", separator);
        String s2 = filePath.replace("\\", separator);
        if (s1.equals(s2)) {
            return "";
        }
        if (!s2.startsWith(s1) || s2.length() <= s1.length()) {
            throw new RuntimeException(filePath + " not sub file for " + dir);
        }
        return filePath.substring(dir.length() + 1).replace("\\", separator);
    }

    public static void mkdirs(File file) {
        if (file != null && !file.exists()) {
            file.mkdirs();
        }
    }

    private static final int bufferSize = 10240;

    @SneakyThrows
    public static Boolean copyStream(InputStream is, OutputStream os) {
        int length;
        byte[] buffer = new byte[bufferSize];
        while ((length = is.read(buffer, 0, bufferSize)) != -1) {
            os.write(buffer, 0, length);
        }
        return true;
    }

    @SneakyThrows
    public static void copyData(InputStream is, DataOutput os) {
        int length;
        byte[] buffer = new byte[bufferSize];
        while ((length = is.read(buffer, 0, bufferSize)) != -1) {
            os.write(buffer, 0, length);
        }
    }

    @SneakyThrows
    public static void writeFile(InputStream is, File file) {
        mkdirs(file.getParentFile());
        @Cleanup OutputStream os = Files.newOutputStream(file.toPath());
        copyStream(is, os);
    }

    public static void writeFile(byte[] data, File file) {
        writeFile(new ByteArrayInputStream(data), file);
    }

    public static void writeFile(String data, File file) {
        writeFile(data.getBytes(), file);
    }

    @SneakyThrows
    public static void writeFile(URL url, File file) {
        URLConnection urlConn = url.openConnection();
        @Cleanup InputStream in = urlConn.getInputStream();
        writeFile(in, file);
    }

    public static String readString(InputStream is) {
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        copyStream(is, os);
        return os.toString();
    }

    @SneakyThrows
    public static String readString(File file) {
        @Cleanup InputStream is = Files.newInputStream(file.toPath());
        return readString(is);
    }


    @SneakyThrows
    public static void fileToOut(File file, OutputStream os) {
        @Cleanup InputStream is = Files.newInputStream(file.toPath());
        copyStream(is, os);
    }


    public static List<Path> findFiles(File root, List<String> rules, Predicate<String> fileFilter) {
        return findFiles(root, path -> {
            String[] paths = path.split(File.separator);
            if (paths.length <= rules.size()) {
                int i = paths.length - 1;
                String code = paths[i];
                String rule = rules.get(i);
                return StringUtil.ruleCheckOne(code, rule);
            }
            return true;
        }, fileFilter);
    }

    @SneakyThrows
    public static List<Path> findFiles(File root, Predicate<String> dirFilter, Predicate<String> fileFilter) {
        List<Path> paths = new ArrayList<>();
        String rootPath = root.getPath();
        Files.walkFileTree(root.toPath(), new SimpleFileVisitor<>() {
            @Override
            public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
                String path = getAbsolutePath(rootPath, dir.toString());
                return "".equals(path) || dirFilter.test(path) ? FileVisitResult.CONTINUE : FileVisitResult.SKIP_SUBTREE;
            }

            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attributes) {
                String filename = getName(file.toString());
                if (fileFilter.test(filename)) {
                    paths.add(file);
                }
                return FileVisitResult.CONTINUE;
            }
        });
        return paths;
    }

    @SneakyThrows
    public static boolean delete(File file) {
        if (file == null || !file.exists()) {
            return true;
        }
        if (file.isDirectory()) {
            File[] subList = file.listFiles();
            if (subList != null) {
                for (File sub : subList) {
                    delete(sub);
                }
            }
        }
        return file.delete();
    }

    @SneakyThrows
    public static InputStream inputStream(File file) {
        return Files.newInputStream(file.toPath());
    }

    @SneakyThrows
    public static OutputStream outputStream(File file) {
        mkdirs(file.getParentFile());
        return Files.newOutputStream(file.toPath());
    }
}
